using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using iTunesLib;
using iGrabber.org.lyricwiki;
using System.Diagnostics;
using System.Threading;
using System.IO;

namespace iGrabber
{
    public partial class frmMain : Form
    {
        #region Delegate methods

        delegate void LoadSaveArtCallback(PictureBox pictureBox, string URL, string path);
        delegate void SetProgressCallback(int value);
        delegate void ChangeControlTextCallback(Control control, string text);
        delegate void ChangeStatusTextCallback(string text);
        delegate void ClearListItemsCallback(ListBox listBox);
        delegate void AddListItemCallback(ListBox listBox, string item);
        delegate void SetSelectedItemCallback(ListBox listBox, int index);
        delegate void SetControlEnabledCallback(Control control, bool enabled);

        #endregion

        #region Private data members

        private iTunesApp iTunes;
        private IITTrackCollection playlist;
        private IITTrackCollection manPlaylist;

        private List<IITTrack> selectedTrackList;
        private List<IITTrack> manSelectedTrackList;
        private List<IITTrack> failedGrabList;
        private List<string> failedAlbumList;
        private List<string[]> results;

        private string[] manSelectedTrack;
        private frmDialog imageDialog;

        private Thread grabThread;
        private Thread manualGrabThread;
        private Thread addArtThread;

        private LyricWiki lyricWiki;

        private bool noImage, noManImage;
        private bool pauseGrab;
        private bool grabArt, grabLyrics;
        private bool overwriteAlbum, overwriteLyrics;

        private string tempArtPath = System.Environment.CurrentDirectory + "\\temp.jpg";
        private string tempManArtPath = System.Environment.CurrentDirectory + "\\temp2.jpg";

        #endregion

        public frmMain()
        {
            InitializeComponent();

            iTunes = new iTunesApp();

            selectedTrackList = new List<IITTrack>();
            manSelectedTrackList = new List<IITTrack>();
            failedGrabList = new List<IITTrack>();
            failedAlbumList = new List<string>();
            manSelectedTrack = new string[4];

            imageDialog = new frmDialog();
            lyricWiki = new LyricWiki();

            noImage = true;
            noManImage = true;
            pauseGrab = false;
            grabArt = true;
            grabLyrics = false;
            overwriteAlbum = false;
            overwriteLyrics = false;

            lblStatus.Text = "Status: Waiting...";
            Program.WriteLog("Started iGrabber");
        }

        // updates the data grid view with the selected tracks
        private void updateGridView()
        {
            List<string> selectedList = new List<string>();

            if (chkNoArt.Checked)
            {
                playlist = iTunes.LibraryPlaylist.Tracks;
            }
            else
            {
                playlist = iTunes.SelectedTracks;
            }

            selectedTrackList.Clear();
            grdTrackList.Rows.Clear();

            if (playlist != null)
            {
                barProgress.Value = 0;
                barProgress.Minimum = 0;
                barProgress.Maximum = playlist.Count;

                lblStatus.Text = "Status: Getting selected tracks from iTunes";
                this.Update();

                for (int i = 1; i < playlist.Count + 1; i++)
                {
                    if (!chkNoArt.Checked || chkNoArt.Checked && playlist[i].Artwork.Count == 0)
                    {
                        if (rdbGroup.Checked)
                        {
                            if (playlist[i].Album != null)
                            {
                                selectedTrackList.Add(playlist[i]);
                                //Debug.WriteLine(playlist[i].Artist + " - " + playlist[i].Name);
                                if (!selectedList.Contains(playlist[i].Album))
                                {
                                    selectedList.Add(playlist[i].Album);
                                    grdTrackList.Rows.Add(playlist[i].Artist, playlist[i].Album);
                                }
                            }
                        }
                        else
                        {
                            if (playlist[i].Album != null)
                            {
                                selectedTrackList.Add(playlist[i]);
                                selectedList.Add(playlist[i].Name);
                                grdTrackList.Rows.Add(playlist[i].Artist, playlist[i].Name, playlist[i].Album);
                            }
                        }
                    }

                    barProgress.PerformStep();
                }

                lblStatus.Text = "Status: Waiting...";
                barProgress.Value = 0;
            }
        }

        private void updateManualGrid()
        {
            manSelectedTrackList.Clear();
            grdManualList.Rows.Clear();

            if (rdbSelected.Checked)
            {
                manPlaylist = iTunes.SelectedTracks;
                if (manPlaylist != null)
                {
                    for (int i = 1; i < manPlaylist.Count + 1; i++)
                    {
                        manSelectedTrackList.Add(manPlaylist[i]);
                        grdManualList.Rows.Add(manPlaylist[i].Artist, manPlaylist[i].Name, manPlaylist[i].Album);
                    }
                }
            }
            else
            {
                for (int i = 0; i < failedGrabList.Count; i++)
                {
                    manSelectedTrackList.Add(failedGrabList[i]);
                    grdManualList.Rows.Add(failedGrabList[i].Artist, failedGrabList[i].Name, failedGrabList[i].Album);
                }
            }

            if (grdManualList.Rows.Count > 0)
            {
                grdManualList.Rows[0].Selected = true;
                manSelectedTrack[0] = grdManualList[0, 0].Value.ToString();
                manSelectedTrack[1] = grdManualList[1, 0].Value.ToString();
                manSelectedTrack[2] = grdManualList[2, 0].Value.ToString();
                manSelectedTrack[3] = grdManualList.SelectedRows[0].Index.ToString();
                lblStatus.Text = "Selected: \"" + manSelectedTrack[0] + " - " + manSelectedTrack[1] + "\"";
            }
        }

        private void showAlbumArt(IITTrack track)
        {
            if (!(track.Artwork == null || track.Artwork[1] == null))
            {
                track.Artwork[1].SaveArtworkToFile(tempArtPath);
                pctAlbumArt.Load(tempArtPath);
                noImage = false;
            }
            else
            {
                pctAlbumArt.Image = iGrabber.Properties.Resources.noimage;
                noImage = true;
            }
        }

        private void showLyrics(IITTrack track)
        {
            IITFileOrCDTrack tempTrack = (IITFileOrCDTrack)track;
            if (tempTrack.Lyrics != null)
            {
                txtLyrics.Text = tempTrack.Lyrics;
            }
            else
            {
                txtLyrics.Text = "";
            }
        }

        private void showManualAlbumArt(IITTrack track)
        {
            if (!(track.Artwork == null || track.Artwork[1] == null))
            {
                track.Artwork[1].SaveArtworkToFile(tempManArtPath);
                pctManArt.Load(tempManArtPath);
                noManImage = false;
            }
            else
            {
                pctManArt.Image = iGrabber.Properties.Resources.noimage;
                noManImage = true;
            }
        }

        private void showManualLyrics(IITTrack track)
        {
            IITFileOrCDTrack tempTrack = (IITFileOrCDTrack)track;
            if (tempTrack.Lyrics != null)
            {
                txtManLyrics.Text = tempTrack.Lyrics;
            }
            else
            {
                txtManLyrics.Text = "";
            }
        }

        // method to grab art / lyrics for tracks
        private void grab()
        {
            string previousTrackAlbum = "";
            failedAlbumList.Clear();

            for (int i = 0; i < selectedTrackList.Count; i++)
            {
                if (pauseGrab)
                {
                    ChangeStatusText("Status: PAUSED");
                    while (pauseGrab) { }
                }
                ChangeStatusText("Status: Grabbing \"" + selectedTrackList[i].Artist + " - " + selectedTrackList[i].Name + "\"");

                if (grabArt)
                {
                    if (selectedTrackList[i].Artwork.Count == 0 || overwriteAlbum)
                    {
                        grabAlbumArt(selectedTrackList[i], previousTrackAlbum);
                    }
                }
                if (grabLyrics)
                {
                    if (((IITFileOrCDTrack)selectedTrackList[i]).Lyrics == null || overwriteLyrics)
                    {
                        grabTrackLyrics(selectedTrackList[i]);
                    }
                }

                previousTrackAlbum = selectedTrackList[i].Album;

                SetProgress(1);
            }
            SetProgress(0);

            ChangeStatusText("Status: Waiting...");
            ChangeControlText(btnGrab, "Grab!");
        }

        // method to grab the album art for a track using the Amazon web API
        private void grabAlbumArt(IITTrack track, string previousTrackAlbum)
        {
            string searchString;
            string albumImage = "";

            searchString = track.Artist + " ";
            searchString += track.Album;

            if (track.Album.Equals(previousTrackAlbum))
            {
                replaceArtwork(track, tempArtPath);
            }
            else if (!failedAlbumList.Contains(track.Album))
            {
                albumImage = AmazonSearch.SearchCDs(searchString);
                LoadSaveArt(pctAlbumArt, albumImage, tempArtPath);
                if (albumImage.Equals(""))
                {
                    noImage = true;
                    failedGrabList.Add(track);
                    failedAlbumList.Add(track.Album);
                }
                else
                {
                    noImage = false;
                    replaceArtwork(track, tempArtPath);
                }
            }

            Thread.Sleep(1000);
        }

        // method to grab the lyrics for a track with the Lyrics Wiki API
        private void grabTrackLyrics(IITTrack track)
        {
            try
            {
                LyricsResult lyricsResult = lyricWiki.getSong(track.Artist, track.Name);
                if (lyricsResult.lyrics.Equals("Not found"))
                {
                    if (!failedGrabList.Contains(track))
                    {
                        failedGrabList.Add(track);
                    }
                }
                else
                {
                    try
                    {
                        ((IITFileOrCDTrack)track).Lyrics = lyricsResult.lyrics;
                        ChangeControlText(txtLyrics, lyricsResult.lyrics);
                    }
                    catch (Exception exception)
                    {
                        Program.WriteLog("Adding lyrics failed for \"" + track.Artist + " - " + track.Name + "\"");
                        Program.WriteLog(exception.Message);
                    }
                }
            }
            catch (System.Net.WebException we)
            {
                ChangeStatusText("Status: Request timed out");
                Program.WriteLog("Web exception caught.");
                Program.WriteLog(we.Message);
            }

            Thread.Sleep(100);
        }

        // Runs in separate thread to manually grab art/lyrics
        private void manualGrab()
        {
            if (chkManArt.Checked)
            {
                ChangeStatusText("Status: Getting art...");
                manualGrabArt();
                SetProgress(1);
            }

            if (chkManLyrics.Checked)
            {
                ChangeStatusText("Status: Getting lyrics...");
                manualGrabLyrics();
            }

            SetProgress(2);
            SetControlEnabled(btnManGrab, true);
            ChangeStatusText("Status: Waiting...");
            SetProgress(0);            
        }

        private void manualGrabArt()
        {
            string requestString = "";
            ClearListItems(lstResults);

            if (chkManURL.Checked)
            {
                LoadSaveArt(pctManArt, txtArtCustom.Text, tempManArtPath);
                noManImage = false;
            }
            else
            {
                if (chkManRequest.Checked)
                {
                    requestString = txtArtCustom.Text;
                }
                else
                {
                    requestString = manSelectedTrack[0] + " " + manSelectedTrack[2];
                }
                ChangeControlText(txtArtCustom, requestString);

                results = AmazonSearch.ManualSearchCDs(requestString);

                if (results != null && results.Count > 0)
                {
                    for (int i = 0; i < results.Count; i++)
                    {
                        AddListItem(lstResults, "#" + (i + 1) + " - " + results[i][0]);
                    }
                    SetSelectedItem(lstResults, 0);
                }
                else
                {
                    noManImage = true;
                    pctManArt.Image = iGrabber.Properties.Resources.noimage;
                    ChangeStatusText("Status: No results found!");
                }
            }
        }

        private void manualGrabLyrics()
        {
            LyricsResult lyricsResult;

            try
            {
                if (chkManRequest.Checked)
                {
                    lyricsResult = lyricWiki.getSong(txtLyricsArtist.Text, txtLyricsSong.Text);
                }
                else
                {
                    ChangeControlText(txtLyricsArtist, manSelectedTrack[0]);
                    ChangeControlText(txtLyricsSong, manSelectedTrack[1]);
                    lyricsResult = lyricWiki.getSong(manSelectedTrack[0], manSelectedTrack[1]);
                }
                ChangeControlText(txtManLyrics, ReplaceLineBreaks(lyricsResult.lyrics));
            }
            catch (System.Net.WebException we)
            {
                ChangeStatusText("Status: Request timed out");
                Program.WriteLog("Web exception caught.");
                Program.WriteLog(we.Message);
            }
        }

        private void addArtwork()
        {
            for (int i = 0; i < grdManualList.SelectedRows.Count; i++)
            {
                SetProgress(1);
                replaceArtwork(manSelectedTrackList[grdManualList.SelectedRows[i].Index], tempManArtPath);
            }

            SetProgress(0);
            ChangeStatusText("Status: Waiting...");
        }

        private void replaceArtwork(IITTrack track, string artPath)
        {
            try
            {
                for (int i = 0; i < track.Artwork.Count; i++)
                {
                    track.Artwork[i + 1].Delete();
                }
                track.AddArtworkFromFile(artPath);
            }
            catch (Exception ex)
            {
                Program.WriteLog("Adding art failed for \"" + track.Artist + " - " + track.Name + "\"");
                Program.WriteLog(ex.Message);
            }
        }

        private string ReplaceLineBreaks(string text)
        {
            return text.Replace("\n", "\r\n");
        }

        private string ReverseReplaceLineBreaks(string text)
        {
            return text.Replace("\r\n", "\n");
        }

        #region Safe code for cross-thread calls on Windows forms

        private void LoadSaveArt(PictureBox pictureBox, string URL, string path)
        {
            // InvokeRequired compares the thread ID of the
            // calling thread to the thread ID of the creating thread.
            // If these threads are different, it returns true.
            if (pictureBox.InvokeRequired)
            {
                LoadSaveArtCallback callback = new LoadSaveArtCallback(LoadSaveArt);
                this.Invoke(callback, new object[] { pictureBox, URL, path });
            }
            else
            {
                if (URL.Equals(""))
                {
                    pictureBox.Image = iGrabber.Properties.Resources.noimage;
                }
                else
                {
                    try
                    {
                        pictureBox.Load(URL);
                        pictureBox.Image.Save(path);
                    }
                    catch (System.Net.WebException we)
                    {
                        Program.WriteLog("Web exception caught.");
                        Program.WriteLog(we.Message);
                    }
                }
            }
        }

        private void SetProgress(int value)
        {
            // InvokeRequired compares the thread ID of the
            // calling thread to the thread ID of the creating thread.
            // If these threads are different, it returns true.
            if (this.barProgress.ProgressBar.InvokeRequired)
            {
                SetProgressCallback callback = new SetProgressCallback(SetProgress);
                this.Invoke(callback, new object[] { value });
            }
            else
            {
                if (value == 0)
                {
                    barProgress.Value = 0;
                }
                else if (value == barProgress.Maximum)
                {
                    barProgress.Value = value;
                }
                else
                {
                    barProgress.Value += value;
                }
            }
        }

        private void ChangeControlText(Control control, string text)
        {
            // InvokeRequired compares the thread ID of the
            // calling thread to the thread ID of the creating thread.
            // If these threads are different, it returns true.
            if (control.InvokeRequired)
            {
                ChangeControlTextCallback callback = new ChangeControlTextCallback(ChangeControlText);
                this.Invoke(callback, new object[] { control, text });
            }
            else
            {
                control.Text = ReplaceLineBreaks(text);
            }
        }

        private void ChangeStatusText(string text)
        {
            // InvokeRequired compares the thread ID of the
            // calling thread to the thread ID of the creating thread.
            // If these threads are different, it returns true.
            if (lblStatus.Owner.InvokeRequired)
            {
                ChangeStatusTextCallback callback = new ChangeStatusTextCallback(ChangeStatusText);
                this.Invoke(callback, new object[] { text });
            }
            else
            {
                lblStatus.Text = text;
                this.Update();
            }
        }

        private void ClearListItems(ListBox listBox)
        {
            // InvokeRequired compares the thread ID of the
            // calling thread to the thread ID of the creating thread.
            // If these threads are different, it returns true.
            if (listBox.InvokeRequired)
            {
                ClearListItemsCallback callback = new ClearListItemsCallback(ClearListItems);
                this.Invoke(callback, new object[] { listBox });
            }
            else
            {
                listBox.Items.Clear();
            }
        }

        private void AddListItem(ListBox listBox, string item)
        {
            // InvokeRequired compares the thread ID of the
            // calling thread to the thread ID of the creating thread.
            // If these threads are different, it returns true.
            if (listBox.InvokeRequired)
            {
                AddListItemCallback callback = new AddListItemCallback(AddListItem);
                this.Invoke(callback, new object[] { listBox, item });
            }
            else
            {
                listBox.Items.Add(item);
            }
        }

        private void SetSelectedItem(ListBox listBox, int index)
        {
            // InvokeRequired compares the thread ID of the
            // calling thread to the thread ID of the creating thread.
            // If these threads are different, it returns true.
            if (listBox.InvokeRequired)
            {
                SetSelectedItemCallback callback = new SetSelectedItemCallback(SetSelectedItem);
                this.Invoke(callback, new object[] { listBox, index });
            }
            else
            {
                listBox.SelectedIndex = index;
            }
        }

        private void SetControlEnabled(Control control, bool enabled)
        {
            // InvokeRequired compares the thread ID of the
            // calling thread to the thread ID of the creating thread.
            // If these threads are different, it returns true.
            if (control.InvokeRequired)
            {
                SetControlEnabledCallback callback = new SetControlEnabledCallback(SetControlEnabled);
                this.Invoke(callback, new object[] { control, enabled });
            }
            else
            {
                control.Enabled = enabled;
            }
        }

        #endregion

        #region Event handlers for the Windows form

        private void btnUpdate_Click(object sender, EventArgs e)
        {
            updateGridView();
        }

        private void rdbShowAll_CheckedChanged(object sender, EventArgs e)
        {
            grdTrackList.Columns.Clear();
            grdTrackList.Columns.Add("Artist", "Artist");
            grdTrackList.Columns.Add("Track", "Track");
            grdTrackList.Columns.Add("Album", "Album");
        }

        private void rdbGroup_CheckedChanged(object sender, EventArgs e)
        {
            grdTrackList.Columns.Clear();
            grdTrackList.Columns.Add("Artist", "Artist");
            grdTrackList.Columns.Add("Album", "Album");
        }

        private void grdTrackList_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            if (grabArt || grabLyrics)
            {
                if (e.RowIndex != -1)
                {
                    // find the first track in the selected album
                    if (rdbGroup.Checked)
                    {
                        string album = grdTrackList[1, e.RowIndex].Value.ToString();
                        for (int i = 1; i < playlist.Count + 1; i++)
                        {
                            if (playlist[i].Album != null && playlist[i].Album.Equals(album)
                                && playlist[i].Artist == grdTrackList[0, e.RowIndex].Value.ToString())
                            {
                                showAlbumArt(playlist[i]);
                                showLyrics(playlist[i]);
                                break;
                            }
                        }
                    }
                    // use the track name
                    else
                    {
                        for (int i = 1; i < playlist.Count + 1; i++)
                        {
                            if (playlist[i].Name.Equals(grdTrackList[1, e.RowIndex].Value.ToString())
                                && playlist[i].Artist == grdTrackList[0, e.RowIndex].Value.ToString())
                            {
                                showAlbumArt(playlist[i]);
                                showLyrics(playlist[i]);
                                break;
                            }
                        }
                    }
                }
            }
        }

        private void pctAlbumArt_Click(object sender, EventArgs e)
        {
            if (!noImage)
            {
                imageDialog.DisplayArt(tempArtPath);
                imageDialog.ShowDialog();
            }
        }

        private void btnGrab_Click(object sender, EventArgs e)
        {
            if (btnGrab.Text.Equals("Grab!"))
            {
                barProgress.Minimum = 0;
                barProgress.Maximum = selectedTrackList.Count;

                btnGrab.Text = "Pause";
                lblStatus.Text = "Status: Grabbing...";
                grabThread = new Thread(new ThreadStart(grab));
                grabThread.Start();
            }
            else if (btnGrab.Text.Equals("Pause"))
            {
                pauseGrab = true;
                btnGrab.Text = "Resume";
            }
            else if (btnGrab.Text.Equals("Resume"))
            {
                pauseGrab = false;
                btnGrab.Text = "Pause";
            }
        }

        private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            Program.WriteLog("Closing iGrabber.");
            if (grabThread != null && grabThread.IsAlive)
            {
                grabThread.Abort();
            }
        }

        private void chkArt_CheckedChanged(object sender, EventArgs e)
        {
            grabArt = chkArt.Checked;
        }

        private void chkLyrics_CheckedChanged(object sender, EventArgs e)
        {
            grabLyrics = chkLyrics.Checked;
        }

        private void chkOverwriteArt_CheckedChanged(object sender, EventArgs e)
        {
            overwriteAlbum = chkOverwriteArt.Checked;
        }

        private void chkOverwriteLyrics_CheckedChanged(object sender, EventArgs e)
        {
            overwriteLyrics = chkOverwriteLyrics.Checked;
        }

        private void btnManUpdate_Click(object sender, EventArgs e)
        {
            updateManualGrid();
        }

        private void btnManGrab_Click(object sender, EventArgs e)
        {
            barProgress.Minimum = 0;
            barProgress.Maximum = 2;
            barProgress.Value = 0;

            btnManGrab.Enabled = false;
            manualGrabThread = new Thread(new ThreadStart(manualGrab));
            manualGrabThread.Start();
        }

        private void grdManualList_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            manSelectedTrack[0] = grdManualList[0, e.RowIndex].Value.ToString();
            manSelectedTrack[1] = grdManualList[1, e.RowIndex].Value.ToString();
            manSelectedTrack[2] = grdManualList[2, e.RowIndex].Value.ToString();
            manSelectedTrack[3] = e.RowIndex.ToString();

            lblStatus.Text = "Selected: \"" + manSelectedTrack[0] + " - " + manSelectedTrack[1] + "\"";

            showManualAlbumArt(manSelectedTrackList[e.RowIndex]);
            showManualLyrics(manSelectedTrackList[e.RowIndex]);
        }

        private void grdManualList_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            grdManualList_CellClick(null, e);
        }

        private void lstResults_SelectedIndexChanged(object sender, EventArgs e)
        {
            try
            {
                pctManArt.Load(results[lstResults.SelectedIndex][1]);
                noManImage = false;
                pctManArt.Image.Save(tempManArtPath);
            }
            catch (System.Net.WebException we)
            {
                Program.WriteLog("Web exception caught.");
                Program.WriteLog(we.Message);
            }
        }

        private void pctManArt_Click(object sender, EventArgs e)
        {
            if (!noManImage)
            {
                imageDialog.DisplayArt(tempManArtPath);
                imageDialog.ShowDialog();
            }
        }

        private void btnAddArt_Click(object sender, EventArgs e)
        {
            barProgress.Minimum = 0;
            barProgress.Maximum = grdManualList.SelectedRows.Count;
            barProgress.Value = 0;
            lblStatus.Text = "Status: Adding art to " + grdManualList.SelectedRows.Count + " tracks...";

            addArtThread = new Thread(new ThreadStart(addArtwork));
            addArtThread.Start();
        }

        private void btnAddLyrics_Click(object sender, EventArgs e)
        {
            try
            {
                ((IITFileOrCDTrack)manSelectedTrackList[int.Parse(manSelectedTrack[3])]).Lyrics = ReverseReplaceLineBreaks(txtManLyrics.Text);
                lblStatus.Text = "Status: Added lyrics to \"" + manSelectedTrack[0] + " - " + manSelectedTrack[1] + "\"";
            }
            catch (Exception ex)
            {
                Program.WriteLog("Adding lyrics failed for \"" + manSelectedTrack[0] + " - " + manSelectedTrack[1] + "\"");
                Program.WriteLog(ex.Message);
            }
        }

        #endregion
    }
}